package spitfire.ksim.evaluation;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import spitfire.ksim.node.KGateway;
import spitfire.ksim.node.KSensor;
import spitfire.ksim.node.KSensorNode;
import spitfire.ksim.simulation.KSimulation;
import spitfire.ksim.util.TPoint;


public class EvaluationClass {

	private int groupSize = 1;
	private KSimulation simulation;
	private int timeTick = 60000;
	private int refreshPeriod = 0;
	private ArrayList<NodeInfo> initNodeInfoList = new ArrayList<NodeInfo>();
	private ArrayList<NodeInfo> testNodeInfoList = new ArrayList<NodeInfo>();
	private List<EvalRecord> recordList = new CopyOnWriteArrayList<EvalRecord>();
//	private long revertPoint = 24 * 3600 * 1000; //24 hours 
//	private String gatewayAlgo = "Representative";
//	private double ratioSum;
	StringBuilder sb = new StringBuilder();
	
	// gui stuff
	private int row;
	private int col;
	private int width = 40;
	private int height = 40;
	public static final int MAXROW = 10;
	public static final int MAXCOL = 10;
	
	public EvaluationClass(KSimulation sim) {
		simulation = sim;
	}
	
//	public void setGatewayAlgo(String gatewayAlgo) {
//		this.gatewayAlgo = gatewayAlgo;
//	}
//
//	public String getGatewayAlgo() {
//		return gatewayAlgo;
//	}

	public void addInitNode(String npfPath, Map<String, String> typeSdMap) {
		initNodeInfoList.add(new NodeInfo(npfPath, typeSdMap));
	}
	
	public boolean hasInitNode(String npfPath) {
		for (NodeInfo info : initNodeInfoList) {
			if (info.npfPath.equals(npfPath)) {
				return true;
			}
		}
		return false;
	}
	
	public void addTestNode(String npfPath, Map<String, String> typeSdMap) {
		testNodeInfoList.add(new NodeInfo(npfPath, typeSdMap));
	}
	
	public void loadConfig(String path) {
		try {
			BufferedReader br = new BufferedReader(new FileReader(path));
			try {
				String line = br.readLine();
				while (line.equals("") || line.startsWith("#")) {
					// omit empty lines and comment lines 
					line = br.readLine();
				}
				groupSize = Integer.parseInt(line);
				line = br.readLine();
//				while (line.equals("") || line.startsWith("#")) {
//					// omit empty lines and comment lines 
//					line = br.readLine();
//				}
//				String algoName = line;
//				setGatewayAlgo(algoName);
				boolean init = true;
				while ((line = br.readLine()) != null) {
					if (line.equals("") || line.startsWith("#")) {
						// omit empty lines and comment lines 
						continue;
					}
					if (line.equalsIgnoreCase("Init:")) {
						init = true;
						continue;
					} else if (line.equalsIgnoreCase("Test:")) {
						init = false;
						continue;
					}
					String[] splited = line.split("#");
					File f = new File(splited[0]);
					if (!f.isFile()) {
						System.out.println(f.getPath() + " not exists, omitted");
						continue;
					}
					Map<String, String> typeSdMap = new HashMap<String, String>();
					int i = 1;
					while (i < splited.length) {
						typeSdMap.put(splited[i].trim(), splited[i+1].trim());
						i += 2;
					}
					if (init) {
						addInitNode(splited[0].trim(), typeSdMap);
					} else {
						addTestNode(splited[0].trim(), typeSdMap);
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	public void setupScenario() {
		KGateway gateway = simulation.addGateway(getNextLoc());
//		gateway.setRequestAlgorithm(gatewayAlgo);
//		gateway.getFuzzyDb().callback = new EvalCallBack() {
//			@Override
//			public void callback(String sAddr, String annotatedSD, double score) {
//				if (!NpfRecordMap.containsKey(sAddr)) {
//					NpfRecordMap.put(sAddr, new EvalRecord());
//				}
////				if (NpfRecordMap.get(sAddr).similarity.containsKey(annotatedSD)) {
////					System.err.println("bammm!!!!!!!!!!!!!");
////				}
//				//similarity will not be updated even after
//				//	the test for this sensor is over
//				NpfRecordMap.get(sAddr).similarity.put(annotatedSD, score);
//			}
//
//			private String nextSAddr(String sAddr) {
//				int nextAddr = Integer.parseInt(sAddr.substring(1)) + 1;
//				return "s" + nextAddr;
//			}
//		};
		
		sb.append("Initial sensors:\n");
		sb.append(String.format("%19s %14s %6s %14s %14s %10s\n", "ID", "Type", "", "SD",
				"", ""));
		for (Iterator<NodeInfo> iterator = initNodeInfoList.iterator(); iterator.hasNext();) {
			NodeInfo info = iterator.next();
			String path = info.npfPath;
			Map<String, String> typeSdMap = info.typeSdMap;
			KSensorNode sNode = simulation.addSensorNode(path, getNextLoc());
			List<KSensor> sensorList = sNode.getSensorList();
			for (KSensor sensor : sensorList) {
				String sd = typeSdMap.get(sensor.getSensorType());
				if (sd == null) {
					sensor.setPowerOn(false);
				} else {
					sensor.setPowerOn(true);
					sensor.setDescription(sd);
					sb.append(String.format("%19s %14s %6s %14s %14s %10s\n",
							sNode.getNodeId(), sensor.getSensorType(), "", sd, "", ""));
				}
			}
		}
	}

	private TPoint getNextLoc() {
		TPoint point = new TPoint(col * width + 80, row * height + 80);
		col++;
		if (col >= MAXCOL) {
			col = 0;
			row++;
		}
		return point;
	}

	public void runEvaluation() {
		simulation.startSimulation();
		simulation.pauseSimulation();
		simulation.setRefreshPeriod(refreshPeriod);
		simulation.setTimeTick(timeTick);

		Random rand = new Random();
		while (testNodeInfoList.size() > 0) {
			int testGroupSize = groupSize;
			if (testNodeInfoList.size() < testGroupSize) {
				testGroupSize = testNodeInfoList.size();
			}
			// Add a test group
			ArrayList<KSensor> testingSensorList = new ArrayList<KSensor>();
			for (int j = 0; j < testGroupSize; j++) {
				int indx = rand.nextInt(testNodeInfoList.size());
				NodeInfo info = testNodeInfoList.get(indx);
				testNodeInfoList.remove(indx);
				String npfPath = info.npfPath;
				Map<String, String> typeSdMap = info.typeSdMap;
				KSensorNode sNode = simulation.addSensorNode(npfPath, getNextLoc());
				List<KSensor> sensorList = sNode.getSensorList();
				for (KSensor sensor : sensorList) {
					String desiredSD = typeSdMap.get(sensor.getSensorType());
					if (desiredSD == null) {
						sensor.setPowerOn(false);
					} else {
						sensor.setPowerOn(true);
						EvalRecord record = new EvalRecord();
						record.npfPath = npfPath;
						record.nodeId = sNode.getNodeId();
						record.nodeAddr = sNode.getNetAddr();
						record.sensorType = sensor.getSensorType();
						record.desiredSD = desiredSD;
						recordList.add(record);
						testingSensorList.add(sensor);
					}
				}
			}

			simulation.resumeSimulation();

			// waiting for testing group to get annotated
			for (KSensor testSensor : testingSensorList) {
				long testStart = simulation.getSimTime();
				while ("Unannotated".equals(testSensor.getDescription())) {
					if (simulation.getSimTime() - testStart > 10L*24*3600*1000) {
						System.err.println("Unannotated, skip");
						break;
					}
					try {
						TimeUnit.MILLISECONDS.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// Simulation done
					if (simulation.isStopped()) {
//						throw  new RuntimeException("not enough data");
						simulation.startSimulation();
						testStart = simulation.getSimTime();
//						break;
					}
				}
				EvalRecord record = findRecord(recordList,
						testSensor.getHostNode().getDatapath(),
						testSensor.getSensorType());
				if (record == null) {
					throw new RuntimeException();
				}
				record.annotatedSD = testSensor.getDescription();
			}

			simulation.pauseSimulation();
			
			// Do stat
			int correctCount = 0;
			for (int i = 0; i < recordList.size(); i++) {
				String SD = recordList.get(i).annotatedSD;
				String desiredSD = recordList.get(i).desiredSD;
				if (SD.equals(desiredSD)) {
					recordList.get(i).correct = true;
					correctCount++;
				}
			}
			double correctRate = correctCount * 1.0 / recordList.size();

			System.err.format("%10s %10s %6s\n", "TestNum", "Correct", "Rate");
			System.err.format("%10d %10d %.4f\n", recordList.size(),
					correctCount, correctRate);

		}
		simulation.stopSimulation();

//		stat();
		int correctCount = 0;
		for (EvalRecord record : recordList) {
			if (record.annotatedSD.equals(record.desiredSD)) {
				correctCount++;
			}
		}
		System.err.println();
		System.err.println("Correct: " + correctCount);
		System.err.println("Totel: " + recordList.size());
		System.err.println("Correct / Total: " + correctCount * 1.0 / recordList.size());
//		System.err.println("Ratio average for correct: " + ratioSum / correctCount);
		System.err.println();

		System.err.println(sb);

		System.err.println("Test sensors:");
		System.err.format("%19s %14s %6s %14s %14s %10s %s\n",
				"ID", "Type", "CR",
				"annotatedSD", "desiredSD", "Addr", "");
		int nodeCount = 0;
		String prevNodeId = null;
		for (EvalRecord record : recordList) {
			if (!record.nodeId.equals(prevNodeId)) {
				nodeCount++;
				prevNodeId = record.nodeId;
				if (nodeCount - groupSize == 1) {
					System.err.println();
					nodeCount = 1;
				}
			}
			
			System.err.format("%19s %14s %6s %14s %14s %10s %s\n",
					record.nodeId, record.sensorType, 
					record.correct, record.annotatedSD, record.desiredSD,
					record.nodeAddr, "");
		}
		// System.err.println(NpfRecordMap);
		System.exit(0);
	}
	
	private EvalRecord findRecord(List<EvalRecord> recordList, String datapath, String sensorType) {
		for (EvalRecord evalRecord : recordList) {
			if (evalRecord.npfPath.equals(datapath) && evalRecord.sensorType.equals(sensorType)) {
				return evalRecord;
			}
		}
		return null;
	}

	/**
	 * 
	 */
//	private void stat() {
//		for (EvalRecord record : recordList) {
//			Map<String, Double> similarity = record.similarity;
//			double sumScore = 0;
//			for (Iterator<String> it = similarity.keySet().iterator(); it.hasNext();) {
//				String key= it.next();
//				sumScore += (Double)similarity.get(key);
//			}
//			double ratio = similarity.get(record.annotatedSD) / sumScore;
//			if (Double.isNaN(ratio)) {
//				ratio = 1.0;
//			}
//			record.ratio = ratio; 
//			ratioSum += ratio;
//		}
//	}
}
